package com.hollis公众号.禁止把SimpleDateFormat定义为static类型;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

/**正常情况下，以上代码输出结果应该是100。但是实际执行结果是一个小于100的数字。
原因就是因为SimpleDateFormat作为一个非线程安全的类，被当做了共享变量在多个线程中进行使用，这就出现了线程安全问题。
我们跟一下SimpleDateFormat类中format方法的实现其实就能发现端倪：见图。
SimpleDateFormat中的format方法在执行过程中，会使用一个成员变量calendar来保存时间。这其实就是问题的关键。
由于我们在声明SimpleDateFormat的时候，使用的是static定义的。那么这个SimpleDateFormat就是一个共享变量，随之，SimpleDateFormat中的calendar也就可以被多个线程访问到。
假设线程1刚刚执行完calendar.setTime把时间设置成2018-11-11，还没等执行完，线程2又执行了calendar.setTime把时间改成了2018-12-12。这时候线程1继续往下执行，拿到的calendar.getTime得到的时间就是线程2改过之后的。
除了format方法以外，SimpleDateFormat的parse方法也有同样的问题。
所以，不要把SimpleDateFormat作为一个共享变量使用。
 *
 */
public class Main {

	/**
	 * 定义一个全局的SimpleDateFormat
	 */
	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	/**
	 * 使用ThreadFactoryBuilder定义一个线程池
	 */
	private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();

	private static ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024),
			namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

	/**
	 * 定义一个CountDownLatch，保证所有子线程执行完之后主线程再执行
	 */
	private static CountDownLatch countDownLatch = new CountDownLatch(100);

	public static void main(String[] args) throws InterruptedException {
		// 定义一个线程安全的HashSet
		Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
		for (int i = 0; i < 100; i++) {
			// 获取当前时间
			Calendar calendar = Calendar.getInstance();
			int finalI = i;
			pool.execute(() -> {
				// 时间增加
				calendar.add(Calendar.DATE, finalI);
				// 通过simpleDateFormat把时间转换成字符串
				String dateString = simpleDateFormat.format(calendar.getTime());
				// 把字符串放入Set中
				dates.add(dateString);
				// countDown
				countDownLatch.countDown();
			});
		}
		// 阻塞，直到countDown数量为0
		countDownLatch.await();
		// 输出去重后的时间个数
		System.out.println(dates.size());
	}

}
